<?php

namespace App\Admin\Traits;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\DB;

trait ModelTree
{
    /**
     * 子节点的排序
     *
     * @var array
     */
    protected static $branchOrder = [];

    /**
     * 存储父节点id的字段名
     *
     * @var string
     */
    protected $parentColumn = 'parent_id';

    /**
     * 节点标题的字段名
     *
     * @var string
     */
    protected $titleColumn = 'title';

    /**
     * 存储排序的字段名
     *
     * @var string
     */
    protected $orderColumn = 'order';

    /**
     * 追加查询的回调
     *
     * @var \Closure
     */
    protected $queryCallback;

    /**
     * 获取所有直接子节点
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany;
     */
    public function children()
    {
        return $this->hasMany(static::class, $this->getTitleColumn());
    }

    /**
     * 获取直接父节点
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function parent()
    {
        return $this->belongsTo(static::class, $this->getParentColumn());
    }

    /**
     * 获取父节点字段名
     *
     * @return string
     */
    public function getParentColumn()
    {
        return $this->parentColumn;
    }

    /**
     * 设置父节点字段名
     *
     * @param string $column 字段名
     */
    public function setParentColumn($column)
    {
        $this->parentColumn = $column;
    }

    /**
     * 获取标题字段名
     *
     * @return string
     */
    public function getTitleColumn()
    {
        return $this->titleColumn;
    }

    /**
     * 设置标题字段名
     *
     * @param string $column 字段名
     */
    public function setTitleColumn($column)
    {
        $this->titleColumn = $column;
    }

    /**
     * 获取排序字段名
     *
     * @return string
     */
    public function getOrderColumn()
    {
        return $this->orderColumn;
    }

    /**
     * 设置排序字段名
     *
     * @param string $column 字段名
     */
    public function setOrderColumn($column)
    {
        $this->orderColumn = $column;
    }

    /**
     * 设置额外查询的回调
     *
     * @param \Closure|null $query
     * @return $this
     */
    public function withQuery(\Closure $query = null)
    {
        $this->queryCallback = $query;

        return $this;
    }

    /**
     * 获取生成好的树状结构数组
     *
     * @return array
     */
    public function toTree()
    {
        return $this->buildNestedArray();
    }

    /**
     * 递归构建树状结构
     *
     * @param array $nodes 树结构所有的节点
     * @param int $parentId 父节点id
     * @return array
     */
    protected function buildNestedArray(array $nodes = [], $parentId = 0)
    {
        $branch = [];

        if (empty($nodes)) {
            $nodes = $this->allNodes();
        }

        foreach ($nodes as $node) {
            if ($node[$this->parentColumn] == $parentId) {
                $children = $this->buildNestedArray($nodes, $node[$this->getKeyName()]);

                if ($children) {
                    $node['children'] = $children;
                }

                $branch[] = $node;
            }
        }

        return $branch;
    }

    /**
     * 获取树结构所有元素
     *
     * @param null|\Illuminate\Database\Query\Builder $query 查询
     * @return array
     */
    public function allNodes($query = null)
    {
        $orderColumn = DB::getQueryGrammar()->wrap($this->getOrderColumn());
        $byOrder = $orderColumn . ' = 0, ' . $orderColumn;

        $query = $query ?: $this;

        if ($this->queryCallback instanceof \Closure) {
            $query = call_user_func($this->queryCallback, $query);
        }

        return $query->orderByRaw($byOrder)->select($this->treeAttributes())->get()->toArray();
    }

    /**
     * 树结构选中需要查询出的字段名数组
     *
     * @return array
     */
    protected function treeAttributes()
    {
        return ['*'];
    }

    /**
     * 设置树状结构中节点的排序
     *
     * @param array $order
     */
    protected static function setBranchOrder(array $order)
    {
        static::$branchOrder = array_flip(array_flatten($order));

        static::$branchOrder = array_map(function ($item) {
            return ++$item;
        }, static::$branchOrder);
    }

    /**
     * 从树状数组中递归保存节点的排序数据
     *
     * @param array $tree 树结构数组
     * @param int $parentId 父节点id
     */
    public static function saveOrder($tree = [], $parentId = 0)
    {
        if (empty(static::$branchOrder)) {
            static::setBranchOrder($tree);
        }

        // TODO 以后是否可以查出所有结点,递归构建update的sql语句,一条语句保存?
        foreach ($tree as $branch) {
            $node = static::find($branch['id']);

            $node->{$node->getParentColumn()} = $parentId;
            $node->{$node->getOrderColumn()} = static::$branchOrder[$branch['id']];
            $node->save();

            if (isset($branch['children'])) {
                static::saveOrder($branch['children'], $branch['id']);
            }
        }
    }

    /**
     * 获取用于下拉选择的树结构
     *
     * @param \Illuminate\Database\Query\Builder $query
     * @return array
     */
    public function scopeSelectOptions($query)
    {
        $nodes = $this->allNodes($query);

        $options = $this->buildSelectOptions($nodes);

        // TODO 并不是所有的树结构都用这个词,或者并不都有顶级
        return collect($options)->prepend('顶级', 0)->all();
    }

    /**
     * 递归构建下拉列表用的树状结构
     *
     * @param array $nodes 所有节点
     * @param int $parentId 父节点id
     * @param string $prefix 子节点名称前面的前缀
     * @return array
     */
    protected function buildSelectOptions(array $nodes = [], $parentId = 0, $prefix = '')
    {
        $indent = str_repeat('　', 2);
        $prefix = $prefix ?: $indent;

        $options = [];

        if (empty($nodes)) {
            $nodes = $this->allNodes();
        }

        foreach ($nodes as $node) {
            $node[$this->getTitleColumn()] = $prefix . $node[$this->getTitleColumn()];
            if ($node[$this->parentColumn] == $parentId) {
                $children = $this->buildSelectOptions($nodes, $node[$this->getKeyName()], $prefix . $indent);

                $options[$node[$this->getKeyName()]] = $node[$this->getTitleColumn()];

                if ($children) {
                    $options += $children;
                }
            }
        }

        return $options;
    }

    /**
     * @inheritdoc
     */
    public function delete()
    {
        $this->where($this->parentColumn, $this->getKey())->delete();

        return parent::delete();
    }

    /**
     * @inheritdoc
     */
    protected static function boot()
    {
        parent::boot();

        // 保存前判断数据中是否有_order字段,如果有,表示只是保存树结构的顺序
        static::saving(function (Model $branch) {

            if (Request::has('_order')) {
                $order = Request::input('_order');

                Request::offsetUnset('_order');

                static::tree()->saveOrder($order);

                return false;
            }

            return $branch;
        });
    }
}